import input
import pickle
import log
import time
import processExecutor
import lruDict

class Env:
    def __init__(self):
        self.mode = 'Release'
        self.tombFile = None
        self.executor = processExecutor.ProcessExecutor()
        self.deadCache = lruDict.LruDict(1024)
        self.bornCache = lruDict.LruDict(1024)
        self.lastCheckParentCount = 1

    def getCache(self, key):
        born = self.bornCache.get(key)
        return born if born else self.deadCache.get(key)

class Context:
    def __init__(self):
        self.eraYears = 99
        self.eras = 0
        self.parentIndex = 0
        self.cellIndex = 1
        self.parents = []
        self.children = []
        self.populationLimit = 8000
        self.newCellFlag = False
        self.lastCheckInputTime = 0
 
class Cell:
    def __init__(self, ctx, p, v, o, c):
        self.parent = p
        self.version = v
        if len(o) < 1024:
            self.origin = o
        else:
            self.origin = o[-1023:]
        self.code = c
        self.childrenCount = 0
        self.progenyCount = 0
        self.parentIndex = 0
        self.eras = ctx.eras
        self.age = 0
        self.cellIndex = ctx.cellIndex
        self.codeLen = len(c)

    def key(self):
        return '[{}-{}]'.format(self.eras, self.cellIndex)

    def partite(self, ctx, env):
        self.age = self.age + 1
        local_vars = {'s': self.code}
        try:
            r = env.executor.exec(self.code, local_vars, 'r')
        except TimeoutError as e:
            log.warning('time out cell:{}'.format(str(self)))
            if self.version == 1:
                raise e
            else:
                return None    
        except Exception as e:
            if self.version == 1:
                raise e
            else:
                return None
        if r == None:
            return None
        ctx.cellIndex = ctx.cellIndex + 1
        born = Cell(ctx, self, self.version + 1, self.origin +'-'+ self.key(), r)
        env.bornCache.put(born.key(), born)
        return born
    def __str__(self):
        return '[version={},eras={},cellIndex={},origin={},age={},childrenCount={},progenyCount={},fertility={}]'.format(self.version, self.eras, self.cellIndex, self.origin, self.age, self.childrenCount, self.progenyCount, self.fertility())
 
    def detail(self):
        return '[version={},eras={},cellIndex={},origin={},age={},childrenCount={},progenyCount={},fertility={}\n{}]'.format(self.version, self.eras, self.cellIndex, self.origin, self.age, self.childrenCount, self.progenyCount, self.fertility(), self.code)
    
    def fertility(self):
        return self.childrenCount / self.age
 
def dump(ctx):
    printOverview(ctx)
    with open('ctx.pkl','wb') as f:
        pickle.dump(ctx, f)
    log.info('dump done parents:{}'.format(len(ctx.parents)))
def load():
    try:
        with open('ctx.pkl','rb') as f:
            return pickle.load(f)
    except Exception as e:
        print('load failed', e)     
    return Context()
  
def partite(ctx, env):
    ps = ctx.parents.copy()
    if len(ps) == 0:
        log.error('partite but empty parents')
        exitHandle(ctx, env)
    for p in ps:
        try:
            newOne = p.partite(ctx, env)
            if newOne == None:
                ctx.parents.remove(p)  
            else:  
                ctx.children.append(newOne)
        except Exception as e:
            if len(ctx.parents) == 1:
                log.warning('last parent partite failed ' + p.detail())
                log.warning(repr(e))
            else:    
                ctx.parents.remove(p)
    nl = ctx.children.copy()
    for c in nl:
        try:
            newChild = c.partite(ctx, env)
            if newChild != None:
                p = c.parent
                while p != None:
                    p.progenyCount = p.progenyCount + 1
                    p = p.parent
                c.parent.childrenCount = c.parent.childrenCount + 1
                ctx.parentIndex = ctx.parentIndex + 1
                c.parentIndex = ctx.parentIndex
                ctx.children.append(newChild)
                ctx.parents.append(c)
                ctx.newCellFlag = True
        except Exception as e:
            pass
        ctx.children.remove(c)

def cmpKey(cell):
    return cell.fertility()

def tombBury(env, dead):
    if dead.progenyCount > 1 and dead.fertility() > 0:
        if env.tombFile == None:
            try:
                env.tombFile = open('tomb.txt', 'a')
            except Exception as e:
                log.warning('tomb open Failed')
        log.warning('tomb tombBury cell:{}'.format(str(dead)))        
        env.tombFile.write(dead.detail())
        env.tombFile.write('\n')
        env.deadCache.put(dead.key(), dead)

def survivalOfFittest(ctx, env):
    dep = len(ctx.parents) - ctx.populationLimit
    if dep > 0:
        log.warning('parents:{}, parentMaxLimit:{} do obsolete'.format(len(ctx.parents), ctx.populationLimit))
        oldParents = ctx.parents.copy()
        oldParents.sort(key = cmpKey)
        for i in range(0, dep):
            dead = oldParents[i]
            ctx.parents.remove(dead)
            tombBury(env, dead)
        log.info('obsolete done parents:{}'.format(len(ctx.parents)))

    if len(ctx.children) > ctx.populationLimit + ctx.populationLimit:
        ctx.children.clear()

 
def printOverview(ctx):
    log.info('eras:{} parents:{} children:{} parentIndex:{} cellIndex:{}'.format(ctx.eras, len(ctx.parents),
                                                                                  len(ctx.children), ctx.parentIndex,
                                                                                  ctx.cellIndex))

def printParents(ctx):
    for i, p in enumerate(ctx.parents):
        print(str(i) + '-' + str(p))

def safeExecScript(ctx, env, code):
    try:
        local = {'ctx': ctx, 'env': env}
        exec(code, {}, local)
    except Exception as e:
        print(e)

def inputCheck(ctx, env):
    pc = len(ctx.parents)
    deltaTime = time.time() - ctx.lastCheckInputTime
    if deltaTime < 10:
        return
    if pc - env.lastCheckParentCount > 20:
        if env.mode == 'Release':
            inputTime = 3
        else:
            inputTime = 6000
        log.warning('parents boom!{} {}'.format(pc, env.lastCheckParentCount))
    else:
        inputTime = 3
    env.lastCheckParentCount = pc
    ctx.lastCheckInputTime = time.time()
    survivalOfFittest(ctx, env)
    if ctx.newCellFlag:
        dump(ctx)
        log.info('new Cell dump!')
        ctx.newCellFlag = False
    printOverview(ctx)
    r = input.input_with_timeout(str(inputTime) + '秒内输入命令，g=继续，q=停止，p=暂停(默认 g):', 'g', inputTime)
    if r == 'q':
        exitHandle(ctx, env)
    elif r == 'p':
        r2 = 'p'
        while r2 != 'g':
            r2 = input.input_with_timeout('''输入命令(默认 p)
    g=恢复
    o=打印概览
    p=暂停
    parent=打印父节点
    parents=打印所有父节点
    children=打印子节点
    era=修改纪元年数
    obsolete=立即执行淘汰算法
    limit=种群上限
    cache=尝试查看Cache节点
    exec=运行脚本                                    
    exit=退出
:''', 'p', 6000)
            if r2 == 'o':
                printOverview(ctx)
            elif r2 == 'parent':
                r3 = input.input_with_timeout('10秒内输入父节点索引，0～{}:'.format(len(ctx.parents) - 1), 0, 10)
                log.info(ctx.parents[int(r3)].detail())
                r2 = 'p'
            elif r2 == 'parents':
                printParents(ctx)
                r2 = 'p'
            elif r2 == 'children':
                r3 = input.input_with_timeout('10秒内输入父节点索引，0～{}:'.format(len(ctx.children) - 1), 0, 10)
                log.info(ctx.children[int(r3)].detail())
                r2 = 'p'
            elif r2 == 'era':
                r3 = input.input_with_timeout('10秒内输入新的循环次数，现值{}:'.format(ctx.eraYears), ctx.eraYears, 10)
                ctx.eraYears = int(r3)
                r2 = 'p'
            elif r2 == 'obsolete':
                survivalOfFittest()
                r2 = 'p'
            elif r2 == 'cache':
                r3 = input.input_with_timeout('20秒内输入cache key(like[eras-cellIndex]):', '', 20)
                node = env.getCache(r3)
                if node == None:
                    print('不存在cache')
                else:
                    print(node.detail())
                r2 = 'p'
            elif r2 == 'limit':
                r3 = input.input_with_timeout('10秒内输入新的种群上限，现值{}:'.format(ctx.populationLimit), ctx.populationLimit, 10)
                ctx.populationLimit = int(r3)
                r2 = 'p'
            elif r2 == 'exec':
                r3 = input.input_with_timeout('60秒内输入脚本(参数 ctx, env) :', 'print("nothing")', 60)
                safeExecScript(ctx, env, r3)
                r2 = 'p'
            elif r2 == 'exit':
                exitHandle(ctx, env)
            else:
                 pass
    else:
        pass

def exitHandle(ctx, env):
    dump(ctx)
    log.info('Evolution stopped')
    if env.tombFile != None:
        env.tombFile.close()
    env.executor.quit()
    exit()

if __name__ == '__main__':
    log.info('Evolution start')
    ctx = load()
    env = Env()

    if len(ctx.parents) == 0 :
        originalCell = Cell(ctx, None, 1, '0', open('cell.py', 'r').read())
        ctx.parents.append(originalCell)  
    while(True):
        inputCheck(ctx, env)
        i = 0
        while i < ctx.eraYears:
            partite(ctx, env)
            inputCheck(ctx, env)
            i = i + 1
        printOverview(ctx)
        ctx.cellIndex = 0
        ctx.eras = ctx.eras + 1
    exitHandle(ctx, env)